// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * lombo_spiscreen.c - Driver of LomboTech SPI SCREEN
 *
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/math64.h>
#include <linux/spi/spi.h>
#include <mach/debug.h>

#define SCREEN_SIZE	0x10000

struct lombo_screen {
	struct spi_device  *spi;
	struct mutex  lock;
	struct device  dev;

	const char  *name;

	u32  flags;
	u32  freq;
	u32  spi_mode;
	u32  page_size;

	int (*write)(const u_char *buf, size_t len);
};

static struct lombo_screen *screen;

/**
 * __write_buf_16 - write buffer to chip in 16 bits write.
 * @spi: pointer to spi device.
 * @buf: data buffer
 * @len: number of bytes to write
 *
 * Return 0 if success, !0 error.
 */
int __write_buf_16(struct spi_device *spi,
				size_t buf, u32 len)
{
	int err = 0;
	struct spi_transfer t;
	struct spi_message m;
	struct lombo_spi_operate_para esop;

	memset(&t, 0, sizeof(t));
	memset(&esop, 0, sizeof(esop));
	spi_message_init(&m);

	esop.inst_len_invalid_flag = 1;
	esop.data_reduce_bit = 7;
	esop.ff = FRAME_FORMAT_STANDARD_SPI;

	t.bits_per_word = 16;
	t.len = len;
	t.tx_buf = (u16 *)buf;
	t.esop = &esop;
	spi_message_add_tail(&t, &m);

	err = spi_sync(spi, &m);
	if (err < 0) {
		PRT_ERR("write from:0x%p, len:%d failed\n",
				(size_t *)buf, len);
		return err;
	}

	return err;
}

/**
 * lombo_screen_write -.
 * @len: length for buf.
 * @buf: buffer for write.
 *
 * Return 0 if success, !0 error.
 */
int lombo_screen_write(const u_char *buf, size_t len)
{
	int err = 0;
	unsigned int i;
	unsigned int page_size;

	if (!screen)
		return -ENOMEM;

	mutex_lock(&screen->lock);

	if (len <= screen->page_size) {
		err = __write_buf_16(screen->spi, (size_t)buf, len);
		if (err) {
			PRT_ERR("__write_buf_16 err, ret=%u\n", err);
			err = __LINE__;
			goto out;
		}
	} else {
		page_size = screen->page_size;
		err = __write_buf_16(screen->spi, (size_t)buf, page_size);
		if (err) {
			PRT_ERR("__write_buf_16 err, ret=%u\n", err);
			err = __LINE__;
			goto out;
		}
		for (i = page_size; i < len; i += page_size) {
			page_size = len - i;
			if (page_size > screen->page_size)
				page_size = screen->page_size;
			err = __write_buf_16(screen->spi, (size_t)(buf + i),
								page_size);
			if (err) {
				PRT_ERR("__write_buf_16 err, ret=%u\n", err);
				err = __LINE__;
				goto out;
			}
		}
	}
out:
	mutex_unlock(&screen->lock);

	return err;
}

/**
 * lombo_screen_probe - probe function of spi.
 * @spi: Pointer to the SPI device.
 *
 * return 0 if success, !0 error.
 */
static int lombo_screen_probe(struct spi_device *spi)
{
	int err = 0;
	struct device_node *np = spi->dev.of_node;

	screen = kzalloc(sizeof(struct lombo_screen), GFP_KERNEL);
	if (!screen) {
		PRT_ERR("malloc screen failed\n");
		return -ENOMEM;
	}

	screen->spi = spi;
	screen->page_size = SCREEN_SIZE;
	mutex_init(&screen->lock);
	dev_set_drvdata(&spi->dev, screen);

	err = of_property_read_u32(np, "spi-max-frequency",
						&screen->freq);
	if (err) {
		PRT_ERR("Fail to get frequency\n");
		goto err_get_frequency;
	}

	screen->name = dev_name(&spi->dev);
	screen->dev.parent = &spi->dev;
	screen->write = lombo_screen_write;

	PRT_INFO("spi_screen name=%s\n", screen->name);

	return 0;

err_get_frequency:
	kfree(screen);

	return err;

}

static int lombo_screen_remove(struct spi_device *spi)
{
	kfree(screen);
	return 0;
}

static const struct of_device_id lombo_screen_match[] = {
	{ .compatible = "lombo,screen", },
	{},
};
MODULE_DEVICE_TABLE(of, lombo_screen_match);

struct spi_driver lombo_screen_driver = {
	.driver = {
		.name = "lombo_screen",
		.owner = THIS_MODULE,
		.of_match_table = lombo_screen_match,
	},
	.probe = lombo_screen_probe,
	.remove = lombo_screen_remove,
};

static int __init lombo_screen_init(void)
{
	return spi_register_driver(&lombo_screen_driver);
}
module_init(lombo_screen_init);

static void __exit lombo_screen_exit(void)
{
	spi_unregister_driver(&lombo_screen_driver);
}
module_exit(lombo_screen_exit);

MODULE_AUTHOR("lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Driver for LomboTech SPI SCREEN");
MODULE_LICENSE("GPL");

